package Model;

import java.awt.Point;

// To generate randomly the terrain, we choose to use the Perlin Noise Algorithm which permits to do coherent random.
// The principle is to generate noise with discrete values and join the points with interpolation.
// After that, we sum those functions calculated with different parameters to obtain the coherent noise function.

public class TerrainGenerator
{
	// ATTRIBUTE
	private int step;
	// number of elementary noises
	private int nbOctaves;
	private int height;
	private int length;
	// values of the noise
	private double[][] values;
	
	// CONSTRUCTOR
	public TerrainGenerator(int step, int height, int length, int nbOctaves) 
	{
		this.step = step;
		this.height = height;
		this.length = length;
		this.nbOctaves = nbOctaves;
		int maxHeight = (int) Math.ceil(this.height * Math.pow(2, this.nbOctaves-1) / this.step);
		int maxLength = (int) Math.ceil(this.length * Math.pow(2, this.nbOctaves-1) / this.step);
		this.values = new double[maxHeight][maxLength];
		
		// generate a random noise
		for (int i = 0; i < maxHeight ; i++)
		{
			for (int j = 0; j < maxLength ; j++)
			{
				this.values[i][j] = Math.random();
			}
		}
	}
	
	// OWN FUNCTIONS
	public double noise(int i, int j)
	{
		return this.values[i][j];
	}
	
	// cosine interpolation of the square abcd (2D); points vary between 0 and 1 => use of (1-cos(pi*x))/2 which also varies between 0 and 1
	public double cosInterpolation2D(double a, double b, double c, double d, double x, double y)
	{
		double x1 = this.cosInterpolation1D(a, b, x);
		double x2 = this.cosInterpolation1D(c, d, x);
		return this.cosInterpolation1D(x1, x2, y);
	}

	public double cosInterpolation1D(double a, double b, double x)
	{
		double k = (1 - Math.cos(Math.PI * x)) / 2;
		return a * (1 - k) + b * k;
	}
	
	// cubic interpolation
	public double cubicInterpolation2D(double p00, double p01, double p02, double p03,
			double p10, double p11, double p12, double p13,
			double p20, double p21, double p22, double p23,
			double p30, double p31, double p32, double p33,
			double x, double y)
	{
		double ci0 = this.cubicInterpolation1D(p00, p01, p02, p03, x);
		double ci1 = this.cubicInterpolation1D(p10, p11, p12, p13, x);
		double ci2 = this.cubicInterpolation1D(p20, p21, p22, p23, x);
		double ci3 = this.cubicInterpolation1D(p30, p31, p32, p33, x);
		
		return this.cubicInterpolation1D(ci0, ci1, ci2, ci3, y);
	}
	
	public double cubicInterpolation1D(double p0, double p1, double p2, double p3, double x)
	{
		double a = p3 - p2 - p0 + p1;
		double b = p0 - p1 - a;
		double c = p2 - p0;
		double d = p1;
		
		return a * Math.pow(x, 3) + b * Math.pow(x,2) + c * x + d;
	}
	
	// interpolation of the noise
	public double noiseFunction(double x, double y)
	{
		int i = (int) Math.floor(x / this.step);
		int j = (int) Math.floor(y / this.step);
		//System.out.println( Math.floor(y / this.step));
		//System.out.println( Math.floor(x / this.step));
		return this.cosInterpolation2D(noise(i, j), noise(i + 1, j), noise(i, j + 1), noise(i + 1, j + 1), (x / this.step) % 1, (y / this.step) % 1);
	}
	
	// function of coherent noise : sum of noiseFunctions with different parameters
	// we calculate several noise function with a variation of the step and the amplitude
	// each time, we divide the step by 2 and multiply the amplitude by a parameter p between 0 and 1 called persistence
	// divide the step by 2 is equal to multiply x and y by 2
	// to finish we sum all those functions
	// the number of functions calculated is called number of octaves
	public double coherentNoise(double x, double y, double persistence)
	{
		double sum = 0;
		double p = 1;
		int f = 1;
		
		for (int i = 0; i < this.nbOctaves; i++)
		{
			sum += p * this.noiseFunction(x * f, y * f);
			p *= persistence;
			
			f *= 2;
		}
		// we divide the sum by the maximal amplitude to keep the values between 0 and 1
		sum *= (1 - persistence) / (1 - p);
		return sum;
	}

}
